package sim.core;

import java.awt.*;

import sim.util.*;
import sim.graphs.*;
import static sim.core.Simulation.* ;

/**
 * Represents a communication endpoint that allows explicit sending and implicit
 * reception of messages. <br>
 * Incoming messages are delivered to the MessageHandler object used to
 * construct the EndPoint object. <br>
 * If the destination endpoint is offline the send() method returns false and
 * OnSendFailure() method of MessageHandler is used to also notify the sender.
 * 
 * @author smd
 * 
 */
public abstract class EndPoint implements Displayable {

	public XY pos;
	public Color color;
	protected boolean online = true;
	protected MessageHandler handler ;
	/**
	 * Creates a new EndPoint object
	 * 
	 * @param handler
	 *            Object that will handle incoming messages
	 */
	protected EndPoint( MessageHandler handler) {
		this.handler = handler;
	}

	/**
	 * Computes the one-way delay incurred in sending a message to a given
	 * Endpoint destination. The delay is independent of message length.
	 * 
	 * @param other
	 *            Destination endpoint.
	 * @return Returns the number of seconds needed to deliver a message to the
	 *         destination endpoint.
	 */
	public double distance( EndPoint other) {
		return 1;
	}

	/**
	 * Sends a message to a destination endpoint. If the destination endpoint is
	 * offline the sender is notified instead that sending the message failed...
	 * 
	 * @param dst
	 *            Destination endpoint of the message.
	 * @param m
	 *            Message to be sent.
	 * @return Returns true is the operation succeeded; false if it failed.
	 */
	public boolean sendTo( final EndPoint dst, final Message m) {
		return udpSend( dst, m ) ;
	}
	
	/**
	 * Broadcasts a message to all the endpoints in the network. 
	 * @param m Message to be sent.
	 */
	public void broadcast( final Message m) {
		
		ShortestPathsTree<EndPoint> t = Network.shortestPathsTree(this) ;
		
		for( EndPoint i : Network.endpoints() )
			if (i == this)
				m.deliverTo(this, i.handler);
			else {
				double delay = 10 * t.cost(i) * (1 + Network.Jitter * Simulation.rg.nextDouble()) ;
				new NetworkPacket( this, i, m,  delay );
			}
	}

	/**
	 * Sends a message to a destination endpoint. If the destination endpoint is
	 * offline the sender is notified instead that sending the message failed...
	 * 
	 * @param dst
	 *            Destination endpoint of the message.
	 * @param m
	 *            Message to be sent.
	 * @return Returns true is the operation succeeded; false if it failed.
	 */
	public boolean udpSend( final EndPoint dst, final Message m) {
		//System.out.println( dst + "/" + m ) ;
		
		if( dst == null) return false;

		if( ! this.online) throw new RuntimeException("Node Offline... No more message exchange allowed...");

		if( ! dst.online) {
			new Task( 0) {
				public void run() {
					handler.onSendFailure(dst, m);
				}
			};
			return false;
		}
		
		if( dst == this)
			m.deliverTo(this, dst.handler);
		else {
			double delay = this.distance( dst ) * (1 + Network.Jitter * Simulation.rg.nextDouble()) ;
			new UdpPacket(this, dst, m, delay );
		}
		return true;
	}

	@SuppressWarnings("unchecked")
	public TcpChannel tcpSend( final EndPoint dst, final Message m) {
		if( dst == null ) return null;
		else if( ! dst.online) {
			new Task(0) {
				public void run() {
					handler.onSendFailure(dst, m);
				}
			};
			return null;
		}
		return new TcpChannel(this, dst).tcpOpen(m);
	}

	/**
	 * Used internally to close and free the EndPoint
	 */
	public void dispose() {
		online = false ;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * Called to draw the endpoint in the simulator.
	 * 
	 * @see sim.gui.Displayable#display(java.awt.Graphics2D,
	 *      java.awt.Graphics2D)
	 */
	public void display( Graphics2D gu, Graphics2D gs) {
	}
	
	abstract public EndPoint replace() ;
}

class UdpPacket extends NetworkPacket {
    public UdpPacket( EndPoint src, EndPoint dst, Message payload, double delay ) {
    	super( src, dst, payload, delay ) ;
    }        
}